/*
    Speedbump aka Project Kimbula
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
	More information at http://www.tidytutorials.com/p/speedbump.html
 */

/*	Init
 *	Open
 *	Pre-request
 *	Request
 *	Response
 *	....
 *	Pre-request
 *	Request
 *	Response
 *	....
 *	Open
 *	...
 *	Clear
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/file.h>
#include "mycap.h"
#include "logger.h"
#include "helpers.h"
#include "init.h"
#include "headerhandler.h"
#include "deflate.h"


char* createhostfromurl(const char*);
short readme(FILE** , const char* , const char*, char*);

#define logcap logd<<"CAPX: " 

/*************************************************************************
 * Connect  CAP per thread/process 
 *************************************************************************/
CAP_RETURN MyCAP::ConnectCAP(void** param1, const char* host, char** ip, int* port){

	logd<<"CAP: open "<<host<<"("<<*ip<<")"<<":"<<*port<<lend;
	assert(*param1);
	MyCapConnection* mycapcon = (MyCapConnection*)*param1 ;

	/* Https only allowed ot port 443 */
	if(mycapcon->request.connvalues & MYCAP_CON_HTTPS && *port !=443)
		return CAP_REJECT;

	if(findinlist(&hosts, *ip)==0)
		return CAP_REJECT;

	/*Internal IPs so lets block them*/
	if(strstr(*ip, "10.")-*ip==0 || strstr(*ip, "172.")-*ip==0 || strstr(*ip, "192.168")-*ip==0 )
		return CAP_REJECT;

	return CAP_NOCHANGE;
}

/*************************************************************************
 * Close CAP per thread/process 
 *************************************************************************/
void MyCAP::CloseCAP(void** param1){

	if(param1 && *param1){
		MyCapConnection* mycapcon = (MyCapConnection*)*param1 ;
	
		if(mycapcon->request.url){
			free(mycapcon->request.url);
			mycapcon->request.url=0;	
		}
		free(*param1);
		*param1=0;
	}
}

/*  1 means ok to surf
 *  t means turing
 *  f means show facebook
 *  x means he passed facebook/google and ok to surf
 *  y means he passed facebook/google but not turing	
 *
 *
 *
 */
/*************************************************************************
 * Pre-request
 *************************************************************************/
CAP_RETURN MyCAP::PreRequestCAP(void** param1,  HeaderHandlerInterface* hhi){
	//return CAP_NOCHANGE;
	MyCapConnection* mycapcon;
	char cmd;
	int urlsize;

	const char* ip;
	const char* url;
	const char* host;
	const char* gzipok;

	url	= strdup(hhi->GetRequestLine());
	host	= hhi->GetHeader("Host");
	ip	= hhi->GetHeader("X-Forwarded-For");


	if(!host || !ip)
		return CAP_ERROR;

	logd<<"CAP: prerequest " << ip <<" | "<< host << " | "<<url<<lend;

	if(!*param1){
		*param1 = malloc(sizeof(MyCapConnection));
		mycapcon=(MyCapConnection*)*param1;
		mycapcon->request.url=0;
		mycapcon->request.acceptgzip=0;
	}
	else{
		mycapcon=(MyCapConnection*)*param1;
	}
	mycapcon->request.connvalues=0;
	mycapcon->request.acceptgzip=0;

	if((gzipok=hhi->GetHeader("Accept-Encoding")) && strstr(gzipok, "gzip")){
		mycapcon->request.acceptgzip=1;
	}


	logcap<<host<<" "<<url<<lend;
	//hhi->DisplayHeaders();

	if(hhi->IsConnect()){
		mycapcon->request.connvalues |= MYCAP_CON_HTTPS;
	}

	switch(findinlist(&hosts, host)){
		case 0:	//block
			logcap<<"Host:Block"<<lend;
			mycapcon->request.connvalues |= MYCAP_CON_BLOCK;
			break;
		case 1:	//pass
			logcap<<"Host:Pass"<<lend;
			hhi->RemoveHeader("X-Forwarded-For");
			mycapcon->request.connvalues |= MYCAP_CON_BYPASS;
			break;
		default:	//not there
			switch(findinlist(&urls, url)){
				case 0:
					logcap<<"Url:Block"<<lend;
					mycapcon->request.connvalues |= MYCAP_CON_BLOCK;
					break;
				case 1:
					logcap<<"Url:Pass"<<lend;
					hhi->RemoveHeader("X-Forwarded-For");
					mycapcon->request.connvalues |= MYCAP_CON_BYPASS;
					break;
				default:
					logcap<<"Url:ASS"<<lend;
					if(stristr(mycapcon->request.url, "POST")!=0 && stristr(mycapcon->request.url, "comment")){
						mycapcon->request.connvalues |= MYCAP_CON_BLOCK;
					}
					break;
			}
			break;
	}
	if(!(mycapcon->request.connvalues & MYCAP_CON_BLOCK) &&
		!(mycapcon->request.connvalues & MYCAP_CON_BYPASS)){
		switch(readme(0, ipfile, ip, &cmd)){
			case 0:
				cmd='T';
			case 1:	//in the file
				switch(cmd){
					case '1':	//ok to surf
						hhi->RemoveHeader("X-Forwarded-For");
						logcap<<"OK"<<lend;
						break;
					case '0':	//banned 
						logcap<<"IP Block"<<lend;
						mycapcon->request.connvalues |= MYCAP_CON_CLIENTBLOCK;
						break;
					case 'T':	//show turing 
					case 'F':	//show facebook

					case 'G':	//show google
						logcap<<cmd<<lend;
						
						logcap<<(!(mycapcon->request.connvalues & MYCAP_CON_BYPASS))<<"-"<<findinlist(mymap[cmd], url)<<lend;
						if(!(mycapcon->request.connvalues & MYCAP_CON_BYPASS) && findinlist(mymap[cmd], url)!=1){
							proxylog.ALog("%s %s - BLOCKED:TUR\n", ip, url);

							char *end, *start;
							if(strstr(url, "GET ")==url &&
								((start=strchr(url, ' ')) && (end=strchr(start+1, ' ')))  ){
								char* furl = (char*)calloc(strlen("GET ")+strlen(myurlmap[cmd])+(end-start)+strlen(" HTTP/1.1")+1, 1);
								sprintf(furl, "GET %s", myurlmap[cmd]);		
								memmove(furl+strlen(furl), start+1, end-(start+1));
								sprintf(furl+strlen(furl)," HTTP/1.1");
								hhi->SetRequestLine(furl);
								hhi->ReplaceHeader("Host",myhostmap[cmd]);
								mycapcon->request.connvalues |= MYCAP_CON_BYPASS;
								free(furl);
							}
							else{
								hhi->SetRequestLine(myurlmap[cmd]);
								hhi->ReplaceHeader("Host",myhostmap[cmd]);
								mycapcon->request.connvalues |= MYCAP_CON_BYPASS;
							}
						}

						/*
						if(findinlist(mymap[cmd], url)!=1){
							mycapcon->request.connvalues |= MYCAP_CON_BLOCK;
						}
						else{
							mycapcon->request.connvalues |= MYCAP_CON_BYPASS;

						}
						*/

						break;
					default: // not on the list
						break;
				}
				break;
			case 20:	//not in the file
				//We should only let turing through unless it's bypassed 
				logcap<<(!(mycapcon->request.connvalues & MYCAP_CON_BYPASS))<<"-"<<findinlist(mymap['T'], url)<<lend;
				if(!(mycapcon->request.connvalues & MYCAP_CON_BYPASS) && findinlist(mymap['T'], url)!=1){
					//proxylog.ALog("%s %s - BLOCKED:TUR\n", ip url);
					logcap<<"Setting turing"<<lend;
					hhi->SetRequestLine(myurlmap['T']);
					hhi->ReplaceHeader("Host",myhostmap['T']);
					mycapcon->request.connvalues |= MYCAP_CON_BYPASS;
				}
					//hhi->DisplayHeaders();
				break;

			default:	//error
				mycapcon->request.connvalues |= MYCAP_CON_BLOCK;
				break;
		}
	}
	

	if(mycapcon->request.connvalues & MYCAP_CON_CLIENTBLOCK){
		hhi->ReplaceHeader("Host", blockpagehost);
		hhi->SetRequestLine(blockpage);	
		proxylog.ALog("%s %s - BLOCKED:IP\n",ip,  url);
	}
	if(mycapcon->request.connvalues & MYCAP_CON_BLOCK){
		hhi->ReplaceHeader("Host", blockpagehost);
		hhi->SetRequestLine(blockpage);	
		proxylog.ALog("%s %s - BLOCKED\n", ip, url);
	}

	free((void*)url);

	url	= hhi->GetRequestLine();
	host	= hhi->GetHeader("Host");
	urlsize = strlen(ip)+1+strlen(url)+1+strlen(host)+1;

	if(mycapcon->request.url)
		free(mycapcon->request.url);	
	mycapcon->request.url = (char*)calloc(urlsize, sizeof(char));
	snprintf(mycapcon->request.url, urlsize, "%s %s %s", ip, host, url);

	return CAP_NOCHANGE;
}


/*************************************************************************
 * Request
 *************************************************************************/
CAP_RETURN MyCAP::RequestCAP(void** param1, char** inout, unsigned int* inoutsize, HeaderHandlerInterface* hhi){

	if(!*param1){
		logcap<<"request null param1 "<<hhi->GetRequestLine()<<lend;
		assert(*param1);
	}
	else{
	}
	return CAP_NOCHANGE;
}


/*************************************************************************
 * Pre-response
 *************************************************************************/
CAP_RETURN MyCAP::PreResponseCAP(void** param1,  HeaderHandlerInterface* hhi){
	MyCapConnection* mycapcon;
	logcap<<"preresponse " << hhi->GetRequestLine()<<lend;
	if(*param1){
		mycapcon = (MyCapConnection*)*param1;
		assert(*param1);
		if(mycapcon->request.url){
			proxylog.ALog("%s - %s\n", mycapcon->request.url, hhi->GetRequestLine() );
		}
		else{
			proxylog.ALog("%s - %s\n", "RUBBISH", hhi->GetRequestLine() );
		}
	}
	else{
		assert(0);
	}

	if((mycapcon->request.connvalues & MYCAP_CON_BLOCK) && (mycapcon->request.connvalues & MYCAP_CON_HTTPS)){
		//hhi->SetRequestLine("HTTP/1.1 500 Internal Server Error");
	}
	return CAP_NOCHANGE;
}


/*************************************************************************
 * Response
 *************************************************************************/
CAP_RETURN MyCAP::ResponseCAP(void** param1, char** inout, unsigned int* inoutsize, HeaderHandlerInterface* hhi){
	const char* gzip;
	char* tmps , *p, *q;
	char* out;
	unsigned int outsize;
	const char* contenttype;
	int deflateret;
	char tmps64[64];
	MyCapConnection* mycapcon;
	unsigned int oldsize;

	if(*param1){
		mycapcon = (MyCapConnection*)*param1;

		if(mycapcon->request.connvalues & MYCAP_CON_BYPASS){
			return CAP_NOCHANGE;
		} 
		outsize=5000000;
		if((contenttype=hhi->GetHeader("content-type")) && strstr(contenttype, "text/html")){
			gzip = hhi->GetHeader("Content-Encoding");
			if(gzip && strcmp(gzip, "gzip")==0){
				logcap<<"GZ: Content gzip encoded"<<lend;
				out = (char*)malloc(outsize);
				if((deflateret=http_inflate(*inout, inoutsize, out, &outsize, 0))==0){
					hhi->RemoveHeader("Content-Encoding");
					logd<<"CAP:GZ: Inflate succeeded "<<outsize<<lend;
					logd<<"CAP:GZ: XXX "<<*inoutsize<<" -> "<<outsize<<"  "<<outsize/(*inoutsize)<<"  "<< hhi->GetHeader("Content-type")<<lend;
					tmps = (char*)realloc(*inout, sizeof(char)*outsize);
					memcpy(tmps,out, outsize);
					*inout=tmps;
					*inoutsize=outsize;
					snprintf(tmps64, 64-1, "%d", *inoutsize); 
					hhi->ReplaceHeader("Content-Length", tmps64);
					//printchararray(*inout, *inoutsize, "OUTARRAY");
				}
				else{
					logw<<"CAP:GZ: Inflate failed "<<outsize<<"/"<<*inoutsize<<" - "<<deflateret<<lend;
				}
				free(out);
			}
			if(*inoutsize && headerignoretag && header && !(mycapcon->request.connvalues & MYCAP_CON_BYPASS)){
				if(!strnistrn(*inout, *inoutsize, headerignoretag,strlen(headerignoretag))){
					if(!(tmps=(char*)realloc(*inout,*inoutsize+strlen(header)))){
						logc<<"CAP: Critical mem error "<<(*inoutsize+strlen(header))<<lend;
						return CAP_ERROR;
					}
					else{
						if(strnistrn(tmps, *inoutsize, "<body",5)) {
						}
						if((q=strnistrn(tmps, *inoutsize, "<body",5)) &&
							(p=strnstrn(q, *inoutsize-(q-tmps), ">",1))){
							memmove(p+1+strlen(header), p+1, *inoutsize-((p+1)-tmps));
							memcpy(p+1, header, strlen(header));
							*inoutsize+=strlen(header);
							snprintf(tmps64, 64-1, "%d", *inoutsize); 
							hhi->ReplaceHeader("Content-Length", tmps64);
							*inout=tmps;
							return CAP_MODIFIED;
						}		
						else{
							*inout=tmps;
							return CAP_NOCHANGE;
						}
					}
				}
				else{
					logcap<<"GZ: Header ignore tag was found in the webpage - no content modification"<<lend;
				}
			}
			else{
			}
		}

		outsize=50000;
		oldsize=*inoutsize;
		if(mycapcon->request.acceptgzip && !hhi->GetHeader("Content-Encoding")){
			out = (char*)malloc(outsize);
			if((deflateret=http_deflate(*inout, inoutsize, out, &outsize, 0))==0){
				if(outsize <  oldsize ){
					tmps = (char*)realloc(*inout, sizeof(char)*outsize);
					memcpy(tmps,out, outsize);
					*inout=tmps;
					*inoutsize=outsize;
					snprintf(tmps64, 64-1, "%d", *inoutsize); 
					hhi->ReplaceHeader("Content-Length", tmps64);
					hhi->ReplaceHeader("Content-Encoding", "gzip");
					logcap<<"GZ: Shrunker "<<(int)(*inoutsize*100/oldsize) << " ("<<*inoutsize<<"/"<<oldsize<<") " <<mycapcon->request.url<<lend;	
				}
			}
			free(out);
		}
	}
	else{
		logcap<<"response null param" <<  hhi->GetRequestLine() <<" "<< *inoutsize <<lend;
	}

	return CAP_NOCHANGE;
}


/*************************************************************************
 * Init One off
 *************************************************************************/
CAP_RETURN MyCAP::InitCAP(){
	InitParameters* sections, * initp;
	char tmps8[8];
	int i;
	char* c;
	char* ic;
	mycaplist::iterator iter;
	FILE* f;
	int fsize;

	initp = new InitParameters();
	if(initp->ReadInitFile("mycap.ini")==-1){
		loge<<"MYCAP: Could not load default preproxy.ini file"<<lend;
	}

	initp->DisplayInitParameters();

	ipfile=strdup(initp->GetInitParameter("ipfile"));
	blockpage=strdup(initp->GetInitParameter("block-page"));
	blockpagehost=createhostfromurl(blockpage);

	if((sections=initp->GetSectionByName("White-Domains"))){
		for(i=0; (sprintf(tmps8,"%d",i))&&(c = (char*)sections->GetInitParameter(tmps8)) ; i++)
			hosts.push_back(std::make_pair(strdup(c), 1));	
	}
	if((sections=initp->GetSectionByName("Black-Domains"))){
		for(i=0; (sprintf(tmps8,"%d",i))&&(c = (char*)sections->GetInitParameter(tmps8)) ; i++)
			hosts.push_back(std::make_pair(strdup(c), 0));	
	}
	if((sections=initp->GetSectionByName("Black-Urls"))){
		for(i=0; (sprintf(tmps8,"%d",i))&&(c = (char*)sections->GetInitParameter(tmps8)) ; i++)
			urls.push_back(std::make_pair(strdup(c), 0));	
	}
	if((sections=initp->GetSectionByName("White-Urls"))){
		for(i=0; (sprintf(tmps8,"%d",i))&&(c = (char*)sections->GetInitParameter(tmps8)) ; i++)
			urls.push_back(std::make_pair(strdup(c), 1));	
	}

	const char* schars="GTF";
	for(ic=(char*)schars; *ic!=0 ; ic++){
		sprintf(tmps8,"%c", *ic);
		if((sections=initp->GetSectionByName(tmps8))){
			mymap[*ic]=new mycaplist;
			for(i=0; (sprintf(tmps8,"%d",i))&&(c = (char*)sections->GetInitParameter(tmps8)) ; i++)
				mymap[*ic]->push_back(std::make_pair(strdup(c), 1));    
			myurlmap[*ic]=strdup((char*)sections->GetInitParameter("url"));
			myhostmap[*ic]=createhostfromurl(sections->GetInitParameter("url"));
		}
	}

	header=0;
	if((c=(char*)initp->GetInitParameter("headerfile"))){
		if((f=fopen(c,"r"))){
			fseek(f, 0L , SEEK_END);
			fsize = ftell(f);
			rewind(f);
			if((header = (char*)calloc(fsize+1, 1))){
				if(fread(header , fsize, 1 , f)){
				}
			}
			fclose(f);
		}
	}
	
	headerignoretag=0;
	if((c=(char*)initp->GetInitParameter("headerfileignoretag"))){
		headerignoretag=strdup(c);
	}
	else{
		assert(0);
	}
	//findinlist(mymap['G'], "ninja");
	//printf("%s\n", myurlmap['G']);

	delete initp;

	return CAP_NOCHANGE;
}


/*************************************************************************
 * Find the char in the list 
 *************************************************************************/
short MyCAP::findinlist(mycaplist* m, const char* c){
	mycaplist::iterator iter;		
	for (iter = m->begin(); iter != m->end(); iter++) {
		if(strstr(c, iter->first)){
			return iter->second;
		}
	}
	return -1;

}


/*************************************************************************
 * Clear  One off
 *************************************************************************/
CAP_RETURN MyCAP::ClearCAP(){
	mycaplist::iterator iter;		
	mycapmap::iterator iterx;
	mycapmapurl::iterator itery;

	if(ipfile)
		free(ipfile);
	if(blockpage)
		free(blockpage);
	if(blockpagehost)
		free(blockpagehost);

	for (iter = urls.begin(); iter != urls.end(); iter++)
		free((char*)iter->first);
	urls.clear();
	for (iter = hosts.begin(); iter != hosts.end(); iter++)
		free((char*)iter->first);
	hosts.clear();

	for (itery = myurlmap.begin(); itery != myurlmap.end(); itery++) 
		free((char*)itery->second);
	myurlmap.clear();
	for (itery = myhostmap.begin(); itery != myhostmap.end(); itery++) 
		free((char*)itery->second);
	myhostmap.clear();

	for( iterx = mymap.begin(); iterx != mymap.end(); iterx++){
		for(iter = (iterx->second)->begin(); iter != (iterx->second)->end(); iter++){
			free((char*)iter->first);
		}
		delete iterx->second ;
	}
	mymap.clear();

	if(header)
		free(header);
	if(headerignoretag)
		free(headerignoretag);

	return CAP_NOCHANGE;
}


/*************************************************************************
 * Read the 'ip' file to see if the user is authorized 
 *************************************************************************/
short readme(FILE** xx, const char* fname, const char* ip, char* cmd){
	long fsize;
	char* buffer;
	short retval= 0;
	FILE* f;
	char tmps32[32];
	struct flock lock;
	char* c;
	
	lock.l_type    = F_RDLCK; 
	lock.l_start   = 0;
	lock.l_whence  = SEEK_SET;
	lock.l_len     = 0;        

	*cmd='0';	
	if(!(f=fopen(fname, "r"))){
		return 0;
	}

	if(fcntl(fileno(f),F_SETLK, &lock )!=-1){
		fseek(f, 0L , SEEK_END);
		fsize = ftell(f);
		if(fsize){
			rewind(f);
			if((buffer = (char*)calloc(fsize+1, sizeof(char)))){
				if(fread(buffer , fsize, 1 , f)){
					snprintf(tmps32, 32, "|%s|", ip);
					if((c=strstr(buffer, tmps32))){
						if(c-buffer > 0){
							c--;
							//printf("####\n%s\n%s\n##%c#\n", buffer, tmps32, *c);
							*cmd=*c;
						}
						retval=1;
					}
				}
				free(buffer);
			}
			else{
				retval=-1;
			}
		}
		lock.l_type = F_UNLCK;
		fcntl(fileno(f), F_SETLK, &lock);	
	}
	fclose(f);
	return retval;
}

/*************************************************************************
 * Exract the host from a URL 
 *************************************************************************/
char* createhostfromurl(const char* requestline){
	char* start, *end, *host=0;
	int size;
	if(requestline){
		if((start=strstr(requestline, "://"))|| (start=strstr(requestline, "://"))){
			start+=strlen("://");
			end=start;
			if((end=strstr(end,"/")) ){
				size=end-start +1;
				host=(char*)calloc(size, sizeof(char));			
				memcpy(host, start, size-1);
			}else{
				size=strlen(requestline)-(start-requestline)+1;
				host=(char*)calloc(size, sizeof(char));			
				memcpy(host, start, size-1);
			}
		}
	}
	return host;
}
